package org.dei.perla.rockfall.utils;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

import org.dei.perla.parser.expressions.ConstantBlob;
import org.dei.perla.parser.expressions.ConstantCastException;
import org.dei.perla.parser.expressions.ConstantFloat;
import org.dei.perla.parser.expressions.ConstantID;
import org.dei.perla.parser.expressions.ConstantInteger;
import org.dei.perla.parser.expressions.ConstantString;
import org.dei.perla.parser.expressions.ConstantTimestamp;
import org.dei.perla.sys.device.fpc.dspic.DSPicFPC;
import org.dei.perla.sys.llqruntime.datastructures.Record;
import org.dei.perla.utils.PerlaRuntime;
import org.dei.perla.utils.logger.SimpleLogger;
import org.dei.perla.utils.pipe.Pipe;
import org.dei.perla.utils.waiter.Waitable;
import org.dei.perla.utils.waiter.Waiter;

public class DeviceLogDatabaseDump implements Runnable {
	
	private static final int MAX_RECORDS = 1;
	private Connection conn = null;
	private PreparedStatement pStmMppt;
	private int counter = 0;
	private final Waiter<Pipe<Record>> pRecordWaiter;
	
	/**
	 * Costruisce un nuovo oggetto DatabaseDump
	 * @param parInPipe pipe sulla quale leggere i dati
	 */
	public DeviceLogDatabaseDump(Pipe<Record> parInPipe) {
		pRecordWaiter = new Waiter<Pipe<Record>>();
		pRecordWaiter.start();
		pRecordWaiter.addWaitable(parInPipe.getWaitable());
				
		initializeDB();
	}

	/**
	 * Inizializza la connessione verso MySQL
	 */
	private void initializeDB() {
		PerlaRuntime runtime = PerlaRuntime.getDefaultRuntime();
		String dbname = runtime.getProperty("PERLA_DB");
		String dbauth = runtime.getProperty("PERLA_DB_AUTH");
		
		if(dbname == null)
			dbname = "jdbc:mysql://localhost/perla?";
		
		if(dbauth == null)
			dbauth = "user=perla&password=perla";
		
		try {
			conn = DriverManager.getConnection(dbname + dbauth);
			
			// Inizializzo le varie query...
			pStmMppt = conn.prepareStatement("INSERT INTO dspicLog " +
					"(TIMESTAMP, NODE_ID, UPTIME, DATA_TIMEOUT, PARAMETER_TIMEOUT, ROUND) " +
					"VALUES	(?, ?, ?, ?, ?, ?)");
		
		} catch (SQLException ex) {
			// Gestione errori
			SimpleLogger.log(this.getClass(), "SQLException: " + ex.getMessage());
			SimpleLogger.log(this.getClass(), "SQLState: " + ex.getSQLState());
			SimpleLogger.log(this.getClass(), "VendorError: " + ex.getErrorCode());
		}
	}
	
	/**
	 * Chiudo la connessione con il DB
	 */
	private void closeDB() {
		if (conn != null){
			try {
				conn.close();
				pStmMppt.close();
			} catch(SQLException e) {
				SimpleLogger.log(this.getClass(), "Errore durante la chiusura della connessione al database");
			}
		}
	}
	
	/**
	 * Metodo helper per eseguire INSERT del record
	 * @param tRecord il record da inserire nel database
	 */
	private void sendToDB(Record tRecord) {
		if (conn == null) {
			SimpleLogger.log(this.getClass(), "Impossibile memorizzare su DB, errore durante la connessione al DB");
			return;
		}
		
		try {
			conn.setAutoCommit(false);
			pStmMppt.setLong(1, ((ConstantTimestamp)tRecord.getField("TIMESTAMP")).getValueTimestamp());
			pStmMppt.setInt(2, tRecord.getField("NODE_ID").getValueInt());
			pStmMppt.setInt(3, tRecord.getField("UPTIME").getValueInt());
			pStmMppt.setInt(4, tRecord.getField("DATA_TIMEOUT").getValueInt());
			pStmMppt.setInt(5, tRecord.getField("PARAMETER_TIMEOUT").getValueInt());
			pStmMppt.setInt(6, tRecord.getField("ROUND").getValueInt());
			this.batch(pStmMppt, conn);
			SimpleLogger.log(this.getClass(), "Record memorizzato su DB");
		} catch (SQLException e) {
			SimpleLogger.log(this.getClass(), "Errore durante l'immissione dei dati nel DB: " + e.getMessage());
		} catch (ConstantCastException e) {
			SimpleLogger.log(this.getClass(), "Errore durante l'immissione dei dati nel DB, " +
					"cast Constant non supportato: " + e.getMessage());
		}
	}
	
	private void batch(PreparedStatement preparedStatement, Connection connection) throws SQLException{
		if(this.counter == MAX_RECORDS){
			preparedStatement.addBatch();	
			this.pStmMppt.executeBatch();
			connection.commit();
			SimpleLogger.log(this.getClass(), "Effettuto commit su DB");
			this.counter = 0;
		}
		else{
			preparedStatement.addBatch();
			this.counter++;
		}

	}
	

	public void run() {
		Waitable<Pipe<Record>> waitable;
		Record record;
		
		while(true) {
			waitable = pRecordWaiter.waitNext();
			record = waitable.getParentComponent().dequeue();
			if (record != null) {
				// Memorizzo il record sul database
				sendToDB(record);
			} else {
				// Killpacket ricevuto, chiudo la connessione con il DB,
				// termino le pipe, termino il processo
				pRecordWaiter.removeWaitable(waitable);
				waitable.getParentComponent().stop();
				pRecordWaiter.stop();
				this.closeDB();
				break;
			}
		}
		SimpleLogger.log(this.getClass(), "DBDump terminated");
	}
}
